// Repurposed from my other project

//---------------------------------------------------------------------------------
// Copyright (c) 2008 Metaphor Game Engine Project
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//        claim that you wrote the original software. If you use this software
//        in a product, an acknowledgment in the product documentation would be
//        appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not be
//        misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//        distribution.
//---------------------------------------------------------------------------------

#include <stdexcept>
#include <stdio.h>

#include "VLENetCpp.h"

VLENetHostWrapper::VLENetHostWrapper(ENetHost *host, VLIENetHostListener *listener)
: mHost(host),
  mListener(listener)
{

}

VLENetHostWrapper::~VLENetHostWrapper()
{
  enet_host_destroy(mHost);
}

void VLENetHostWrapper::process()
{
  ENetEvent evt;

  //do a non-blocking read through all the packets we have received
  while (enet_host_service(mHost, &evt, 0) > 0)
  {
    switch (evt.type)
    {
    case ENET_EVENT_TYPE_CONNECT:

      mListener->onConnect(*this, evt.peer);

      break;

    case ENET_EVENT_TYPE_RECEIVE:
      /* Clean up the packet now that we're done using it. */
      mListener->onPacketReceived(*this, evt.peer, *evt.packet, evt.channelID);

      enet_packet_destroy(evt.packet);

      break;

    case ENET_EVENT_TYPE_DISCONNECT:

      mListener->onDisconnect(*this, evt.peer, evt.data);


      /* Reset the peer's client information. */

      evt.peer->data = NULL;
    }
  }
}

void VLENetHostWrapper::sendPacket(ENetPeer *peer, ENetPacket *packet, enet_uint8 channel)
{
  assert(peer);

  int returnValue = enet_peer_send(peer, channel, packet);
  if (returnValue < 0)
  {
    throw std::runtime_error("Packet could not be sent");
  }

  enet_host_flush(mHost);
}

void VLENetHostWrapper::broadcastPacket(ENetPacket *packet, enet_uint8 channel)
{
  enet_host_broadcast(mHost, channel, packet);

  enet_host_flush(mHost);
}

void VLENetHostWrapper::kick(ENetPeer *peer, unsigned int reason)
{
  //disconnect it as expediately as possible
  enet_peer_disconnect_now(peer, 0);
}

void VLENetHostWrapper::disconnect(ENetPeer *peer, unsigned int reason)
{
  //disconnect but let the peer know it was disconnected
  enet_peer_disconnect(peer, 0);
}

void VLENetHostWrapper::disconnectAll(unsigned int reason)
{
  // TODO: disconnect doesn't work unless we reuse the host and continue to
  // connect/disconnect it from places, because graceful connects only happen if think
  // continues to be called

  // For every peer, disconnect from it
  VLU32 peerCount = mHost->peerCount;
  for (VLU32 i = 0; i < peerCount; ++i)
  {
    disconnect(&(mHost->peers[i]));
  }
}

VLENetSystem::VLENetSystem()
{
  // I would love to throw here but I can't - this is a ctor. I will instead
  // throw an error when the user tries to create a server or client
  mENetInitReturnValue = enet_initialize();
}

VLENetSystem::~VLENetSystem()
{
  if (initSuccessful())
  {
    enet_deinitialize();
  }
}

VLENetHostWrapperPtr VLENetSystem::createServer(short port, 
                                            int clientsSupported,
                                            VLIENetHostListener *listener)
{
  ENetAddress address;

  // bind to all local IPs
  address.host = ENET_HOST_ANY;

  address.port = port;

  return createServer(address, clientsSupported, listener);
}

VLENetHostWrapperPtr VLENetSystem::createServer(ENetAddress &addressToBind, 
                                            int clientsSupported,
                                            VLIENetHostListener *listener)
{
  if (!initSuccessful())
  {
    throw std::runtime_error("System was not able to initialize");
  }

  ENetHost *server = 0;

  server = enet_host_create (&addressToBind,   // the address to bind the server host to
                             clientsSupported, // allow up to n clients and/or outgoing connections
                             0,                // 0 on a LAN, otherwise bytes/second outgoing
                             0);               // 0 on a LAN, otherwise bytes/second incoming

  if (server == NULL)
  {
    throw std::runtime_error("An error occurred while trying to create an ENet server host");
  }

  return VLENetHostWrapperPtr(new VLENetHostWrapper(server, listener));
}

VLENetHostWrapperPtr VLENetSystem::createClient(const std::string server, 
                                            short port,
                                            VLIENetHostListener *listener,
                                            bool blockUntilConnected,
                                            long timeout)
{
  if (!initSuccessful())
  {
    throw std::runtime_error("System was not able to initialize");
  }

  ENetHost *client;

  client = enet_host_create (
            NULL, // create a client host
            1,    // only allow 1 outgoing connection
            0,    // 0 on a LAN, otherwise bytes/second outgoing
            0);   // 0 on a LAN, otherwise bytes/second incoming

  if (client == NULL)
  {
    throw std::runtime_error("An error occurred while trying to create an ENet server host");
  }

  ENetAddress address;
  ENetEvent event;
  ENetPeer *peer;

  // Connect to server:port.
  enet_address_set_host(&address, server.c_str());
  address.port = port;

  // Initiate the connection, allocating some channels
  peer = enet_host_connect(client, &address, 16);
  
  if (peer == NULL)
  {
    throw std::runtime_error("No available peers for initiating an ENet connection.\n");
  }
  
  bool callOnConnect = false;

  if (blockUntilConnected)
  {
    /* Wait up to 5 seconds for the connection attempt to succeed. */
    if (!(enet_host_service (client, & event, timeout) > 0 &&
        event.type == ENET_EVENT_TYPE_CONNECT))
    {
        /* Either the 5 seconds are up or a disconnect event was */
        /* received. Reset the peer in the event the 5 seconds   */
        /* had run out without any significant event.            */
        enet_peer_reset (peer);

        VL_THROW1(VLENetException, "Could not connect to remote host");
    }
    else
    {
      //delay the onConnect() call until the ENetHostWrapper is created
      callOnConnect = true;
    }
  }

  VLENetHostWrapperPtr wrapper(new VLENetHostWrapper(client, listener));

  //listener->onConnect(*wrapper.get(), peer);

  return wrapper;
}

#ifndef VL_NET_DO_NOT_CREATE_SYSTEM
VLENetSystem gENetSystem;
#endif
